package com.ifnk.portaljava.dapWeb.socket;

import com.ifnk.portaljava.dapWeb.dapweb.Command;
import edu.emory.mathcs.backport.java.util.Collections;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

public class CommandHistory {
	
	private static Log loger = LogFactory.getLog(CommandHistory.class);
	
	private List<Command> cdrpackages;
	private List<PcapMap> pcapMap;
	
	private Map<Integer, Set<Integer>> receivedId;
	
	private class PcapMap {
		private long timestamp;
		private int pcapno;
	}
	
	private class PcapMapComparator implements Comparator<PcapMap> {

		@Override
		public int compare(PcapMap o1, PcapMap o2) {
			if(o1 == o2)
				return 0;
			
			if(o1 == null && o2 != null) {
				return -1;
			}
			if(o2 == null && o1 != null) {
				return 1;
			}
			if(o1.timestamp < o2.timestamp)
				return -1;
			else if(o1.timestamp > o2.timestamp)
				return 1;
			else
				return o1.pcapno - o2.pcapno;
		}
		
	}
	
	public class TimeComparator implements Comparator<Command> {

		@Override
		public int compare(Command o1, Command o2) {
			
			Command pktCommand = new Command();
			int t1 = 0;
			try {
				pktCommand.DecodePktData(o1.getPkt());
				t1 = pktCommand.getTs_s();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			Command pktCommand2 = new Command();
			int t2 = 0;
			try {
				pktCommand2.DecodePktData(o2.getPkt());
				t2 = pktCommand2.getTs_s();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return t1 - t2;
		}
	}
	
	public CommandHistory() {
		cdrpackages = new LinkedList<Command>();
		receivedId = new HashMap<Integer, Set<Integer>>();
		pcapMap = new LinkedList<PcapMap>();
	}
	
	public Command getCommandByTimeIndex(int index) {
		if(index < 0 || index > pcapMap.size()) {
			return null;
		}
		
		/*
		*/
		loger.debug("pcap no:" + index + " ==> command no:" + pcapMap.get(index).pcapno);
		for(int i = 0; i< cdrpackages.size(); i++) {
			loger.debug("command cdr id("+ i + "): " + cdrpackages.get(i).getCdrId() + " " + cdrpackages.get(i).getSequenceNo());
		}
		
		for(int i = 0; i< pcapMap.size(); i++) {
			loger.debug("pcapMap id("+ i + "): " + pcapMap.get(i).pcapno + " " + pcapMap.get(i).timestamp);
		}
		
		return cdrpackages.get(pcapMap.get(index).pcapno);
		
	}
	
	public int getCommandNumber() {
		return cdrpackages.size();
	}
	
	public boolean add(Command c) {
		loger.debug("add command " + c.getCdrId() + " " + c.getSequenceNo());
		
		// TODO: we drop package when time over 2109 12 31
		long t = getCDRPktTimestamp(c);
//		if( (t / 1000) < 1577721600 && newSeqNo(c.getCdrId(), c.getSequenceNo()) && differentCDR(c)) {
		if( newSeqNo(c.getCdrId(), c.getSequenceNo()) && differentCDR(c)) {
			Command cme = Command.cloneme(c);
			cdrpackages.add(cme);
			
			PcapMap p = new PcapMap();
			p.pcapno = cdrpackages.size() - 1;
			p.timestamp = getCDRPktTimestamp(cme);
			
			pcapMap.add(p);
			
			Collections.sort(pcapMap, new PcapMapComparator());
			
			loger.debug("add command success " + c.getCdrId() + " " + c.getSequenceNo());
			return true;
			
		} else
			return false;
	}
	
	
	private boolean differentCDR(Command c) {
		loger.debug("before filter out command:" + c.getCdrId() + " " + c.getTs_s());
		
		for(int i = 0; i < cdrpackages.size(); i++) {
			Command dc = cdrpackages.get(i);
			if (dc.getCdr() != null && dc.getCdr().equals(c.getCdr())) {
				return false;
			}
			if(getCDRPktTimestamp(c) == getCDRPktTimestamp(dc)) {
				loger.debug("time is same");
				byte[] t1 = c.getPkt();
				byte[] t2 = dc.getPkt();
				int j = 0;
				if(t1.length == t2.length) {
					for(j = 22; j < t1.length; j++) {
						if(t1[j] != t2[j])
							break;;
					}
					if(j == t1.length){
						loger.debug("same command, filter out: " + c.getCdrId() + " " + c.getTs_s());
						return false;
					}
				}
			}
		}
		return true;
	}

	private boolean newSeqNo(int cdrid, int no) {
		if(receivedId.containsKey(cdrid)){
			Set<Integer> seqnos = receivedId.get(cdrid);
			// seqnos must not be null!
			if(seqnos.contains(no)) {
				return false;
			} else {
				seqnos.add(no);
				return true;
			}
		}else{
			Set<Integer> seqnos3 = new HashSet<Integer>();
			seqnos3.add(no);
			receivedId.put(cdrid, seqnos3);
			return true;
		}
	}
	
	private long getCDRPktTimestamp(Command c) {
		// first version, not using the ns time
		
		loger.debug(byte2String(c.getPkt()));
		
		int second = DecodeByteArrayToInt(c.getPkt(), 12);
		int nanosecond = DecodeByteArrayToInt(c.getPkt(), 16);
		
		// we lost 10^3 精度
		long t = (long) (second & 0x00000000ffffffff) * 1000 + (nanosecond & 0x00000000ffffffff) /1000000;
		return t;
		
	}
	
    public static String byte2String(byte[] buff) {
        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < buff.length; i++) {
            sb.append(Integer.toHexString(buff[i] & 0xff));
            sb.append(" ");
        }

        return sb.toString();
    }
	
	private static int DecodeByteArrayToInt(byte[] b, int start) {
		return ((b[start] & 0xFF) << 24) + ((b[start + 1] & 0xFF) << 16)
				+ ((b[start + 2] & 0xFF) << 8) + (b[start + 3] & 0xFF);
	}

	public Command getCommandByPcapNo(int pcapNo) {
		if(pcapNo < 0 || pcapNo >= cdrpackages.size())
			return null;
		else
			return cdrpackages.get(pcapNo);
	}

}
